/*
 * Copyright (c) 2016 MariaDB Corporation Ab
 * Copyright (c) 2023 MariaDB plc, Finnish Branch
 *
 * Use of this software is governed by the Business Source License included
 * in the LICENSE.TXT file and at www.mariadb.com/bsl11.
 *
 * Change Date: 2028-05-14
 *
 * On the date above, in accordance with the Business Source License, use
 * of this software will be governed by version 2 or later of the General
 * Public License.
 */

/**
 * @file mxs1071_maxrows.cpp Test of Maxrows filter
 * Initial filter configuration
 *  @verbatim
 *  [MaxRows]
 *  type=filter
 *  module=maxrows
 *  max_resultset_rows=20
 *  max_resultset_size=9000000
 *  debug=3
 *  @endverbatim
 * All the tests executes statemet, prepared statement or stored procedure and checks
 * number of rows in the result sets (multiple result sets possible)
 *
 * Test  1 - max_allowed_packet limit is not hit, simple SELECTs, small table
 * Test  2 - same queries, but bigger table - limit is hit in some cases
 * Test  3 - stored procedure, limit is not hit, single result set
 * Test  4 - stored procedure, limit is not hit, multiple result sets
 * Test  5 - stored procedure, limit is not hit, multiple result sets
 * Test  6 - stored procedure, limit is hit, multiple result sets
 * Test  7 - stored procedure, limit is not hit, long blobs, multiple result sets
 * Test  8 - stored procedure, limit is hit, long blobs, multiple result sets
 * Test  9 - query non-existant table, expect proper error
 * Test 10 - stored procedure, limit could be hit if executed until the end,
 *           multiple result sets, query non-existant table, expect proper error
 *           and result sets generated before error
 * Test 11 - SET @a=4 - empty result set
 * Test 12 - prepared statement, using mysql_stmt_* functions, limit is hit
 *           Test 12 is repeated using mysql_query() function
 * Test 13 - same as Test 12, but limit is not hit
 * Test 14 - prepared statement inside of store procedure, multiple result sets
 *           limit is not hit
 * Test 15 - prepared statement inside of store procedure, multiple result sets
 *           limit is hit
 * Test 16 - SELECT '' as 'A' limit 1 (empty result)
 * Test 17 - multiple result sets with empty result, limit is not hit
 * Test 18 - multiple result sets with empty result, exactly as a limit (20)
 *           (expect 20 result sets)
 * Test 19 - multiple result sets with empty result, limit is hit
 * Test 20 - SELECT long blobs, limit is not hit
 * Test 21 - change max_resultset_size to lower values, SELECT long blobs,
 *           max_resultset_size limit is hit
 */

#include <iostream>
#include <maxtest/blob_test.hh>
#include <maxtest/mariadb_func.hh>
#include <maxtest/sql_t1.hh>
#include <maxtest/testconnections.hh>

using namespace std;

const char* test03_sql =
    " CREATE PROCEDURE multi()\n"
    "BEGIN\n"
    "SELECT x1 FROM t1 LIMIT 2;\n"
    "END";

const char* test04_sql =
    "CREATE PROCEDURE multi() BEGIN\n"
    "SELECT 1;\n"
    "SELECT x1 FROM t1 LIMIT 2;\n"
    "SELECT 1,2,3; \n"
    "END";

const char* test05_sql =
    "CREATE PROCEDURE multi() BEGIN\n"
    "SELECT 1;\n"
    "SELECT x1 FROM t1 LIMIT 8;\n"
    "SELECT 1,2,3; \n"
    "SELECT 1;"
    "END";

const char* test06_sql =
    "CREATE PROCEDURE multi() BEGIN\n"
    "SELECT 1;\n"
    "SELECT x1 FROM t1 LIMIT 18;\n"
    "SELECT 2; \n"
    "SELECT 2;"
    "END";

const char* test07_sql =
    "CREATE PROCEDURE multi() BEGIN\n"
    "SELECT 1,2,3,4;\n"
    "SELECT id, b from long_blob_table order by id desc limit 1;\n"
    "SELECT id, b from long_blob_table order by id desc limit 4;\n"
    "SELECT id, b from long_blob_table order by id desc limit 1;\n"
    "SELECT id, b from long_blob_table order by id desc;\n"
    "SELECT id, b from long_blob_table order by id desc;\n"
    "SELECT 1;\n"
    "END";

const char* test08_sql =
    "CREATE PROCEDURE multi() BEGIN\n"
    "SELECT 1,2,3;\n"
    "SELECT id, b, b from long_blob_table order by id desc limit 1;\n"
    "SELECT 2;\n"
    "SELECT id, b from long_blob_table order by id desc limit 4;\n"
    "SELECT id, b from long_blob_table order by id desc limit 2;\n"
    "SELECT 1;\n"
    "SELECT 1;\n"
    "SELECT x1 FROM t1 LIMIT 8;\n"
    "SELECT 1;\n"
    "SELECT 1,2,3,4;\n"
    "END";

const char* test10_sql =
    "CREATE PROCEDURE multi() BEGIN\n"
    "SELECT 1;\n"
    "SELECT x1 FROM t1 limit 4;\n"
    "select * from dual;\n"
    "set @a=4;\n"
    "SELECT 2;\n"
    "SELECT * FROM t1;\n"
    "END";

const char* test14_sql =
    "CREATE PROCEDURE multi() BEGIN\n"
    "SELECT 1,3;\n"
    "SET @table = 't1';\n"
    "SET @s = CONCAT('SELECT * FROM ', @table, ' LIMIT 18');\n"
    "PREPARE stmt1 FROM @s;\n"
    "EXECUTE stmt1;\n"
    "DEALLOCATE PREPARE stmt1;\n"
    "SELECT 2,4,5;\n"
    "END";

const char* test15_sql =
    "CREATE PROCEDURE multi() BEGIN\n"
    "SELECT 1,3;\n"
    "SET @table = 't1';\n"
    "SET @s = CONCAT('SELECT * FROM ', @table, ' LIMIT 100');\n"
    "PREPARE stmt1 FROM @s;\n"
    "EXECUTE stmt1;\n"
    "DEALLOCATE PREPARE stmt1;\n"
    "SELECT 2,4,5;\n"
    "END";

const char* test17_sql =
    "CREATE PROCEDURE multi() BEGIN\n"
    "SELECT '' as 'A' limit 1;\n"
    "SELECT '' as 'A' limit 10;\n"
    "SELECT '' as 'A';\n"
    "END";

const char* test18_sql =
    "CREATE PROCEDURE multi() BEGIN\n"
    "SELECT '' as 'A' limit 1;\n"
    "SELECT '' as 'A' limit 10;\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A' limit 1;\n"
    "SELECT '' as 'A' limit 10;\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "END";

const char* test19_sql =
    "CREATE PROCEDURE multi() BEGIN\n"
    "SELECT '' as 'A' limit 1;\n"
    "SELECT '' as 'A' limit 10;\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A' limit 1;\n"
    "SELECT '' as 'A' limit 10;\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "SELECT '' as 'A';\n"
    "END";

/**
 * @brief compare_expected Execute sql and compare number of rows in every result set with expected values
 * If number if result sets differs from expected value or number of rows in any result sey differs from
 * given expected value this function calls Test->add_result
 * @param Test TestConnections object
 * @param sql SQL query to execute
 * @param exp_i Expected number of result sets
 * @param exp_rows Array of expected numbers of rows for every result set
 * @return 0 in case of lack of error
 */
int compare_expected(TestConnections* Test, const char* sql, my_ulonglong exp_i, my_ulonglong exp_rows[])
{
    vector<my_ulonglong> rows(30);
    my_ulonglong i;

    Test->reset_timeout();
    execute_query_num_of_rows(Test->maxscale->conn_rwsplit, sql, rows.data(), &i);

    Test->tprintf("Result sets number is %llu\n", i);

    if (i != exp_i)
    {
        Test->add_result(1, "Number of result sets is %llu instead of %llu\n", i, exp_i);
        return 1;
    }

    for (my_ulonglong j = 0; j < i; j++)
    {
        Test->tprintf("For result set %llu number of rows is %llu\n", j, rows[j]);
        if (rows[j] != exp_rows[j])
        {
            Test->add_result(1,
                             "For result set %llu number of rows is %llu instead of %llu\n",
                             j,
                             rows[j],
                             exp_rows[j]);
            return 1;
        }
    }
    return 0;
}

/**
 * @brief compare_stmt_expected Execute prepared statement and compare number of rows in every result set with
 * expected values
 * This function uses mysql_stmt-* functions (COM_STMT_EXECUTE, COM_STMT_FETCH)
 * @param Test TestConnections object
 * @param stmt MYSQL_STMT prepared statement handler
 * @param exp_i Expected number of result sets
 * @param exp_rows Array of expected numbers of rows for every result set
 * @return 0 in case of lack of error
 */
int compare_stmt_expected(TestConnections* Test,
                          MYSQL_STMT* stmt,
                          my_ulonglong exp_i,
                          my_ulonglong exp_rows[])
{
    vector<my_ulonglong> rows(30);
    my_ulonglong i;

    Test->reset_timeout();
    execute_stmt_num_of_rows(stmt, rows.data(), &i);

    Test->tprintf("Result sets number is %llu\n", i);

    if (i != exp_i)
    {
        Test->add_result(1, "Number of result sets is %llu instead of %llu\n", i, exp_i);
        return 1;
    }

    for (my_ulonglong j = 0; j < i; j++)
    {
        Test->tprintf("For result set %llu number of rows is %llu\n", j, rows[j]);
        if (rows[j] != exp_rows[j])
        {
            Test->add_result(1,
                             "For result set %llu number of rows is %llu instead of %llu\n",
                             j,
                             rows[j],
                             exp_rows[j]);
            return 1;
        }
    }
    return 0;
}

/**
 * @brief err_check Print mysql_error() and mysql_errno and compare mysql_errno with given expected value
 * @param Test TestConnections object
 * @param expected_err Expected error code
 */
void err_check(TestConnections* Test, unsigned int expected_err)
{
    Test->tprintf("Error text '%s'' error code %d\n",
                  mysql_error(Test->maxscale->conn_rwsplit),
                  mysql_errno(Test->maxscale->conn_rwsplit));
    if (mysql_errno(Test->maxscale->conn_rwsplit) != expected_err)
    {
        Test->add_result(1,
                         "Error code is not %d, it is %d\n",
                         expected_err,
                         mysql_errno(Test->maxscale->conn_rwsplit));
    }
}

int main(int argc, char* argv[])
{

    vector<my_ulonglong> exp_rows(30);
    MYSQL_STMT* stmt;

    TestConnections* Test = new TestConnections(argc, argv);
    Test->reset_timeout();
    Test->maxscale->connect_rwsplit();
    Test->try_query(Test->maxscale->conn_rwsplit, "SET GLOBAL max_allowed_packet=10000000000");
    Test->maxscale->connect_rwsplit();

    create_t1(Test->maxscale->conn_rwsplit);
    insert_into_t1(Test->maxscale->conn_rwsplit, 1);
    Test->repl->sync_slaves();

    Test->tprintf("**** Test 1 ****\n");


    exp_rows[0] = 16;
    compare_expected(Test, (char*) "select * from t1", 1, exp_rows.data());

    exp_rows[0] = 16;
    compare_expected(Test, (char*) "select * from t1 where fl=0", 1, exp_rows.data());

    exp_rows[0] = 10;
    compare_expected(Test, (char*) "select * from t1 limit 10", 1, exp_rows.data());

    Test->reset_timeout();
    create_t1(Test->maxscale->conn_rwsplit);
    insert_into_t1(Test->maxscale->conn_rwsplit, 3);
    Test->repl->sync_slaves();


    Test->tprintf("**** Test 2 ****\n");
    exp_rows[0] = 0;
    compare_expected(Test, (char*) "select * from t1", 1, exp_rows.data());

    exp_rows[0] = 16;
    compare_expected(Test, (char*) "select * from t1 where fl=0", 1, exp_rows.data());

    exp_rows[0] = 10;
    compare_expected(Test, (char*) "select * from t1 limit 10", 1, exp_rows.data());

    Test->tprintf("**** Test 3 ****\n");
    exp_rows[0] = 2;
    exp_rows[1] = 0;
    Test->try_query(Test->maxscale->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi");
    Test->try_query(Test->maxscale->conn_rwsplit, "%s", test03_sql);
    compare_expected(Test, "CALL multi()", 2, exp_rows.data());

    Test->tprintf("**** Test 4 ****\n");
    exp_rows[0] = 1;
    exp_rows[1] = 2;
    exp_rows[2] = 1;
    exp_rows[3] = 0;
    Test->try_query(Test->maxscale->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi");
    Test->try_query(Test->maxscale->conn_rwsplit, "%s", test04_sql);
    compare_expected(Test, "CALL multi()", 4, exp_rows.data());

    Test->tprintf("**** Test 5 ****\n");
    exp_rows[0] = 1;
    exp_rows[1] = 8;
    exp_rows[2] = 1;
    exp_rows[3] = 1;
    exp_rows[4] = 0;
    Test->try_query(Test->maxscale->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi");
    Test->try_query(Test->maxscale->conn_rwsplit, "%s", test05_sql);
    compare_expected(Test, "CALL multi()", 5, exp_rows.data());

    Test->tprintf("**** Test 6 ****\n");
    exp_rows[0] = 0;

    Test->try_query(Test->maxscale->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi");
    Test->try_query(Test->maxscale->conn_rwsplit, "%s", test06_sql);
    compare_expected(Test, "CALL multi()", 1, exp_rows.data());


    Test->tprintf("LONGBLOB: Trying send data via RWSplit\n");
    Test->repl->connect();
    // test_longblob(Test, Test->maxscales->conn_rwsplit, (char *) "LONGBLOB", 512 * 1024 / sizeof(long
    // int), 17 * 2, 25);
    test_longblob(*Test, Test->repl->nodes[0], "LONGBLOB", 512 * 1024 / sizeof(long int), 17 * 2, 5);
    Test->repl->close_connections();


    Test->tprintf("**** Test 7 ****\n");
    exp_rows[0] = 1;
    exp_rows[1] = 1;
    exp_rows[2] = 4;
    exp_rows[3] = 1;
    exp_rows[4] = 5;
    exp_rows[5] = 5;
    exp_rows[6] = 1;
    exp_rows[7] = 0;

    Test->try_query(Test->maxscale->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi");
    Test->try_query(Test->maxscale->conn_rwsplit, "%s", test07_sql);
    compare_expected(Test, "CALL multi()", 8, exp_rows.data());

    Test->tprintf("**** Test 8 ****\n");
    exp_rows[0] = 0;

    Test->try_query(Test->maxscale->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi");
    Test->try_query(Test->maxscale->conn_rwsplit, "%s", test08_sql);
    compare_expected(Test, "CALL multi()", 1, exp_rows.data());

    Test->tprintf("**** Test 9 ****\n");
    exp_rows[0] = 0;

    compare_expected(Test, "SELECT * FROM dual", 0, exp_rows.data());
    err_check(Test, 1096);

    Test->tprintf("**** Test 10 ****\n");
    exp_rows[0] = 1;
    exp_rows[1] = 4;

    Test->try_query(Test->maxscale->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi");
    Test->try_query(Test->maxscale->conn_rwsplit, "%s", test10_sql);
    compare_expected(Test, "CALL multi()", 2, exp_rows.data());

    err_check(Test, 1096);

    Test->tprintf("**** Test 11 ****\n");
    exp_rows[0] = 0;

    compare_expected(Test, "SET @a=4;", 1, exp_rows.data());
    err_check(Test, 0);

    // Prepared statements

    Test->tprintf("**** Test 12 (C++) ****\n");
    exp_rows[0] = 0;

    stmt = mysql_stmt_init(Test->maxscale->conn_rwsplit);
    if (stmt == NULL)
    {
        Test->add_result(1, "stmt init error: %s\n", mysql_stmt_error(stmt));
    }
    char* stmt1 = (char*) "SELECT * FROM t1";
    Test->add_result(mysql_stmt_prepare(stmt, stmt1, strlen(stmt1)),
                     "Error preparing stmt: %s\n",
                     mysql_stmt_error(stmt));

    compare_stmt_expected(Test, stmt, 1, exp_rows.data());

    mysql_stmt_close(stmt);



    Test->tprintf("**** Test 12 (MariaDB command line client) ****\n");
    exp_rows[0] = 0;
    Test->try_query(Test->maxscale->conn_rwsplit, "SET @table = 't1'");
    Test->try_query(Test->maxscale->conn_rwsplit, "SET @s = CONCAT('SELECT * FROM ', @table)");
    Test->try_query(Test->maxscale->conn_rwsplit, "PREPARE stmt1 FROM @s");
    compare_expected(Test, "EXECUTE stmt1", 1, exp_rows.data());
    Test->try_query(Test->maxscale->conn_rwsplit, "DEALLOCATE PREPARE stmt1");


    Test->tprintf("**** Test 13 (C++)****\n");
    exp_rows[0] = 10;
    exp_rows[1] = 0;
    stmt = mysql_stmt_init(Test->maxscale->conn_rwsplit);
    if (stmt == NULL)
    {
        Test->add_result(1, "stmt init error: %s\n", mysql_stmt_error(stmt));
    }
    char* stmt2 = (char*) "SELECT * FROM t1 LIMIT 10";
    Test->add_result(mysql_stmt_prepare(stmt, stmt2, strlen(stmt2)),
                     "Error preparing stmt: %s\n",
                     mysql_stmt_error(stmt));
    compare_stmt_expected(Test, stmt, 1, exp_rows.data());
    mysql_stmt_close(stmt);

    Test->tprintf("**** Test 13 (MariaDB command line client) ****\n");
    Test->try_query(Test->maxscale->conn_rwsplit, "SET @table = 't1'");
    Test->try_query(Test->maxscale->conn_rwsplit,
                    "SET @s = CONCAT('SELECT * FROM ', @table,  ' LIMIT 10')");
    Test->try_query(Test->maxscale->conn_rwsplit, "PREPARE stmt1 FROM @s");
    compare_expected(Test, "EXECUTE stmt1", 1, exp_rows.data());
    Test->try_query(Test->maxscale->conn_rwsplit, "DEALLOCATE PREPARE stmt1");

    Test->tprintf("**** Test 14 ****\n");
    exp_rows[0] = 1;
    exp_rows[1] = 18;
    exp_rows[2] = 1;
    exp_rows[3] = 0;
    Test->try_query(Test->maxscale->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi");
    Test->try_query(Test->maxscale->conn_rwsplit, "%s", test14_sql);
    compare_expected(Test, "CALL multi()", 4, exp_rows.data());

    Test->tprintf("**** Test 15 ****\n");
    exp_rows[0] = 0;
    Test->try_query(Test->maxscale->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi");
    Test->try_query(Test->maxscale->conn_rwsplit, "%s", test15_sql);
    compare_expected(Test, "CALL multi()", 1, exp_rows.data());

    Test->tprintf("**** Test 16 ****\n");
    exp_rows[0] = 1;
    exp_rows[1] = 0;
    compare_expected(Test, "SELECT '' as 'A' limit 1;", 1, exp_rows.data());

    Test->tprintf("**** Test 17 ****\n");
    exp_rows[0] = 1;
    exp_rows[1] = 1;
    exp_rows[2] = 1;
    exp_rows[3] = 0;
    Test->try_query(Test->maxscale->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi");
    Test->try_query(Test->maxscale->conn_rwsplit, "%s", test17_sql);
    compare_expected(Test, "CALL multi()", 4, exp_rows.data());

    Test->tprintf("**** Test 18 ****\n");
    exp_rows[0] = 1;
    exp_rows[1] = 1;
    exp_rows[2] = 1;
    exp_rows[3] = 1;
    exp_rows[4] = 1;
    exp_rows[5] = 1;
    exp_rows[6] = 1;
    exp_rows[7] = 1;
    exp_rows[8] = 1;
    exp_rows[9] = 1;
    exp_rows[10] = 1;
    exp_rows[11] = 1;
    exp_rows[12] = 1;
    exp_rows[13] = 1;
    exp_rows[14] = 1;
    exp_rows[15] = 1;
    exp_rows[16] = 1;
    exp_rows[17] = 1;
    exp_rows[18] = 1;
    exp_rows[19] = 1;
    exp_rows[20] = 0;
    Test->try_query(Test->maxscale->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi");
    Test->try_query(Test->maxscale->conn_rwsplit, "%s", test18_sql);
    compare_expected(Test, "CALL multi()", 21, exp_rows.data());

    Test->tprintf("**** Test 19 ****\n");
    exp_rows[0] = 0;

    Test->try_query(Test->maxscale->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi");
    Test->try_query(Test->maxscale->conn_rwsplit, "%s", test19_sql);
    compare_expected(Test, "CALL multi()", 1, exp_rows.data());

    Test->tprintf("**** Test 20 ****\n");
    exp_rows[0] = 2;
    exp_rows[1] = 0;
    Test->try_query(Test->maxscale->conn_rwsplit, "SET GLOBAL max_allowed_packet=10000000000");
    compare_expected(Test, "SELECT * FROM long_blob_table limit 2;", 1, exp_rows.data());
    err_check(Test, 0);

    Test->maxscale->close_rwsplit();

    Test->maxscale->ssh_node(
        "sed -i \"s/max_resultset_size=900000000/max_resultset_size=90000/\" /etc/maxscale.cnf",
        true);
    Test->reset_timeout();
    Test->maxscale->restart_maxscale();

    Test->maxscale->connect_rwsplit();

    Test->tprintf("**** Test 21 ****\n");
    exp_rows[0] = 0;
    Test->try_query(Test->maxscale->conn_rwsplit, "SET GLOBAL max_allowed_packet=10000000000");
    compare_expected(Test, "SELECT * FROM long_blob_table limit 1;", 1, exp_rows.data());

    Test->check_maxscale_alive();
    int rval = Test->global_result;
    delete Test;

    return rval;
}
